Skip to content

feat(mcp): add corpus-backed GitHub search and source artifacts - #249

Merged
morluto merged 2 commits into
mainfrom
agent/corpus-backed-github-search
Aug 1, 2026
Merged

feat(mcp): add corpus-backed GitHub search and source artifacts#249
morluto merged 2 commits into
mainfrom
agent/corpus-backed-github-search

Conversation

@morluto

@morluto morluto commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Description

Add provenance-preserving live GitHub thread search and bounded source-file acquisition backed by immutable local corpus artifacts. This addresses two related workflow gaps: live search results previously had no durable, exact handoff, and source provenance could confuse a GitHub blob SHA with the resolved commit SHA.

The adapter now resolves named refs to one authoritative commit, reads source files at that commit, and keeps blob identity separate. github.search_threads persists returned thread observations without claiming repository-wide coverage and stores an immutable github-thread-search.v1 artifact. github.read_source_files stores bounded ordered files and per-item outcomes in a source-bundle.v1 artifact. Both artifacts are exposed through exact, digest-bound local resources/read routes; resource reads do not contact GitHub.

The additive corpus.search_code_batch operation runs up to 20 code queries against one offline corpus revision, preserving each query’s existing coverage and truncation semantics. Live GitHub code search remains intentionally out of scope.

Review order:

  1. internal/github provenance and bounded adapter behavior
  2. internal/app persistence/artifact behavior and offline code batching
  3. internal/mcpcontract/internal/mcpserver tool, schema, and resource wiring
  4. tests and documentation

Testing

  • make verify — passed (uncached short tests, golangci-lint, module tidiness, generated-output checks, and AGENTS validation).
  • go test ./... — passed.
  • make test-race — passed for app, corpus, and workspace packages.
  • Focused HTTP, persistence, digest/resource, coverage-boundary, and offline network-trap regressions were added or updated.

Compatibility and scope

  • Storage and side-effect invariants remain intact.
  • No unrelated cleanup or generated-output churn is included.
  • Existing corpus.search_code remains supported.
  • GitHub mutations and live GitHub code search remain out of scope.

@morluto
morluto marked this pull request as ready for review August 1, 2026 05:18
@cursor

cursor Bot commented Aug 1, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff6e440624

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/github/client.go Outdated
Comment on lines +460 to +461
if text := strings.TrimSpace(opts.Query); text != "" {
queryParts = append(queryParts, text)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject queries that escape the requested repository

When opts.Query contains a scope-changing expression such as OR repo:other/project, appending it verbatim after the fixed repo: qualifier allows GitHub to return results outside owner/repo. The application then marks those results failed while the immutable artifact records them as ordinary items, and they can consume the bounded page and hide valid in-scope matches. Reject or structurally constrain repository-changing qualifiers and boolean expressions before issuing the request.

AGENTS.md reference: AGENTS.md:L5-L6

Useful? React with 👍 / 👎.

Comment thread internal/app/guidance.go
Comment on lines +59 to +61
resolution, _, err := resolver.ResolveRepositoryRef(ctx, ref.Owner, ref.Repo, requestedRef)
if err != nil {
return fmt.Errorf("resolve contribution guidance ref %q: %w", requestedRef, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat empty repositories as having no guidance files

When repository-context synchronization targets a newly created or otherwise empty GitHub repository, resolving its default branch or HEAD through the commits endpoint returns GitHub's empty-repository error rather than a commit. This now aborts the entire context-sync task after metadata has already been written, whereas probing the fixed guidance paths previously treated their 404 responses as an empty, complete guidance set. Handle the empty-repository response by atomically storing an empty guidance snapshot instead of failing the repository.

Useful? React with 👍 / 👎.

Comment thread internal/github/client.go Outdated
Comment on lines +360 to +361
if len(file.Content) > opts.PerFileBytes {
item.Status, item.Bytes, item.Message = "too_large", len(file.Content), fmt.Sprintf("file exceeds %d-byte per-file limit", opts.PerFileBytes)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Classify files above GitHub's inline limit as too large

For files larger than GitHub's Contents API inline-content threshold (about 1 MiB), the response reports no decodable inline content, so GetRepositoryFileAtResolvedRef fails before this length check is reached. Such files are therefore emitted as generic failed items even when they plainly exceed the configured per-file limit, defeating the contract's distinct too_large outcome and recovery semantics. Preserve the provider-reported size/type and return too_large before attempting to decode inline content.

Useful? React with 👍 / 👎.

Comment thread internal/github/client.go Outdated
Comment on lines +380 to +382
contentDigest := sha256.Sum256([]byte(content))
item.Status, item.File, item.StartLine, item.EndLine, item.Bytes, item.ContentSHA = "complete", file, startLine, endLine, len(content), hex.EncodeToString(contentDigest[:])
item.File.Content = content

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject or encode non-UTF-8 source bytes

When a requested file or selected line range contains invalid UTF-8 bytes, this code hashes the original bytes and marks the item complete, but the later encoding/json serialization of the source artifact replaces those bytes with Unicode replacement characters. The persisted content therefore no longer matches content_sha256, silently corrupting the supposedly exact source bundle. Validate UTF-8 and return a distinct failed outcome, or encode arbitrary file bytes reversibly before hashing and persistence.

Useful? React with 👍 / 👎.

@morluto
morluto merged commit 43cbd8f into main Aug 1, 2026
14 checks passed
@morluto
morluto deleted the agent/corpus-backed-github-search branch August 1, 2026 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant